const  fs   = require( "fs" );
const  path = require("path");
module.exports={
	getFiles : getFilesRecursively ,
	readText : readFile ,
	stat : getFileStat ,
	copy
};
void( exports.getFiles  && exports.readText && getFileSize);

/**
 *
 * @param {String} source
 * @param {String} dist
 * @param {Boolean} [mkDir]
 * @return {Promise.<void>}
 */
async function copy( source , dist , mkDir ){

	if( mkDir){
		let sourceDir = path.dirname( source );
		let distDir   = path.dirname( dist );
		await mkDirIfNotExists(  sourceDir );
		await mkDirIfNotExists(  distDir );
	}
	let readStream  = fs.createReadStream( source );
	let writeStream = fs.createWriteStream( dist );
	return new Promise( function( resolve, reject ){

		readStream.pipe( writeStream );

		readStream.on("error" , function( error ){
			try{
				//如果可读流在处理时发生错误，目标可写流 不会 自动关闭。 如果发生错误，需要 手动 关闭所有流以避免内存泄漏。
				writeStream.close();
			}catch ( err ){
				console.error( err );
			}finally {
				reject( error );
			}
		});
		writeStream.on("error" , function( error ){
			 reject( error );
		});

		readStream.on( "end" , function(  ){
			resolve();
		})

	});

}

async function getFileSize(  filePath ){
	let stat = await  getFileStat( filePath ) ;
	if(!stat.isFile)
		return 0 ;
	return stat.size ;
}
function exists( path ){
	return new Promise( function( resolve  ){
		fs.exists( path  , resolve );
	});
}
async function mkDirIfNotExists( dir ){
	const maxLevel=5;
	let dirs = [];
	while( ! await exists(dir) ){
		dirs.push( dir );
		dir = dir.replace( /[\\\/]$/ , "" );
		let newDir = dir.replace( /[^\\\/]+$/,"" );
		if( newDir === dir )
			throw new Error("取上级文件夹出错");
		dir = newDir ;
		if( dirs.length >maxLevel)
			throw new Error(`创建${dir}失败，最多只能创建${maxLevel}级`);
		if( dir==="" || dir==="/")
			break;
	}
	while( dirs.length> 0 ){
		await mkDir( dirs.pop() );
	}
}
function mkDir( dir ){
	return new Promise( function( resolve, reject ){
		console.info(`创建文件夹${dir}`  );
		fs.mkdir( dir ,function( err ){
			if ( err && err.id !=="EEXIST" ){
				let newErr = new Error( `创建文件夹${dir}失败` );
				newErr.stack = err ;
				reject( newErr );
			}else{
				resolve( true );
				console.info(`创建文件夹${dir}成功`  );
			}
		} );
	});
}
/**
 *
 * @param  {String} dirPath
 * @param  {RegExp} [fileExtReg]
 * @param  {Array|String|} [jsFiles]
 * @return {Promise.<Array|String|>}
 */
async function getFilesRecursively( dirPath , fileExtReg,  jsFiles ){

	if( !jsFiles )
		jsFiles = [];

	let stats = await getFileStat( dirPath );
	if( !stats ){
		return jsFiles;
	}
	if( stats.isFile() ){
		if( !fileExtReg ||  fileExtReg.test( dirPath) ){

			jsFiles.push( dirPath  );
			return jsFiles;
		}
	}

	return new Promise( function( resolve ,reject ){

		fs.readdir( dirPath , async function( err ,files ){

			if(err){
				return reject(err);
			}

			for(  let i=0 ; i< files.length ; i++ ){

				let fileName = files[ i ];
				let fullFilePath =  path.join( dirPath ,  fileName );

				let stats = await getFileStat( fullFilePath );
				if(  stats.isFile() ){
					if( !fileExtReg || fileExtReg.test( fileName)  )
						jsFiles.push( fullFilePath  );
				}

				if( fileName.indexOf(".") > -1 || fileName.startsWith(".")  ||  fileName==="node_modules" || fileName==="libs" ){
					continue;
				}


				if( stats.isDirectory() ){
					await getFilesRecursively( fullFilePath  ,fileExtReg, jsFiles );
				}


			}
			resolve( jsFiles );

		});

	})
}

function readFile(  file , options ){
	return new Promise( function( resolve ,reject ){
		fs.readFile( file , options , function( err , result ){
			if( err )
				reject( err );
			else
				resolve( result );
		} );
	});
}

/**
 *
 * @param {String} filePath
 * @return {Promise.< FsStat >}
 */
function getFileStat( filePath ){
	return new Promise( function( resolve ,reject ){
		fs.stat( filePath , function( err, stats ){
			if( !err){
				resolve( stats );
			}else if( err.code === 'ENOENT' ){ //文件不存在
				resolve( null );
			}else{
				reject( err );

			}
		});
	});
}

/**
 * File Stat 类型
 * @constructor
 */
function FsStat(  ){
	let  dev = 0 , ino = 0  , mode = 0  , nlink  = 0 , uid = 0  , gid = 0  ,
		rdev = 0  , size = 0  , blksize  = 0 , blocks  = 0 , atimeMs = 0  ,ctimeMs = 0 ,birthtimeMs=0 ,
		atime = new Date() , mtime = atime , ctime = atime , birthtime = atime  ;
	return {
		dev , ino , mode , nlink , uid , gid ,
		rdev , size , blksize , blocks , atimeMs ,ctimeMs ,birthtimeMs ,
		atime ,mtime ,ctime , birthtime
	};
}
void( FsStat );
